

module SmsRb

    #
    # Reads from a string character-by-character. 
    #
    class StringStream
    
        def initialize(s)
            @str = s;
            @ind = 0;
        end

        def next
            retval = @str[@ind]
            @ind += 1;
            return retval;
        end
        
        def has?
            return @ind < @str.size
        end        
    end

    #
    # Decodes from and encodes to a json string. 
    # Supports following types: 
    #   NilClass
    #   String
    #   TrueClass, FalseClass
    #   Fixnum, Float, Length
    #   Array, Geom::Point3d
    #   Hash
    #
    class Json
        
        #
        # Encodes the given object to a json string. 
        #
        def Encode(obj)
            cl = obj.class
            if cl == NilClass
                return 'null';
            elsif cl == TrueClass
                return 'true';
            elsif cl == FalseClass
                return 'false';
            elsif cl == String
                retval = '"';
                ss = StringStream.new(obj);
                while(ss.has?)
                    ch = ss.next;
                    if (ch == '"'[0])
                        retval += '\"';
                    elsif (ch == "\\"[0])
                        retval += "\\\\";
                    elsif (192 <= ch && ch < 224)
                        ch2 = ss.next;
                        a = ((ch - 192) * 64) + (ch2 - 128);
                        retval += ("\\u%04x" % a);
                    else
                        retval += ("%c" % ch);
                    end
                end
                retval += '"';
                return retval;
            elsif cl == Fixnum || cl == Float || cl == Length
                return obj.to_f.to_s;
            elsif cl == Array 
                retval = []
                obj.each do |e|
                    retval << Encode(e);
                end;
                return '[' + retval.join(',') + ']';
            elsif cl == Hash
                retval = []
                obj.each do |k,v|
                    retval << '"' + k + '":' + Encode(v);
                end;
                return '{' + retval.join(',') + '}';
            elsif cl == Geom::Point3d
                return Encode(obj.to_a);
            end
            raise ("%s" % cl + " : %s" % obj.inspect)
        end
        
        def _IsToken(token)
            return (@str[@index..(@index + token.length - 1)] == token)
        end
        
        def _IsTokens(*tokens)
            tokens.each do |token|
                if _IsToken(token)
                    #_GetToken(token)
                    return true
                end
            end
            return false
        end
        
        def _GetToken(token)
            if _IsToken(token)
                @index += token.length;
                return
            end
            raise(("_GetToken(token) : {%s}" % token) + (" {%s}" % @str[@index..@index + 20]))
        end
        
        def _GetNextCharToken()
            ch = @str[@index..@index];
            @index += 1;
            return ch;
        end
        
        def _GetTokens(*tokens)
            tokens.each do |token|
                if _IsToken(token)
                    _GetToken(token)
                    return token
                end
            end
            raise("_GetTokens(token) : {%s}; " % tokens.inspect + "{%s}" % token.inspect)
        end

        def _SkipToken(token)
            if _IsToken(token)
                _GetToken(token)
            end
        end
        
        def _SkipWhiteSpace()
            _SkipToken(' ')
            
        end
        
        def _DecodeString()
            _SkipWhiteSpace();
            _GetToken('"');
            retval = "";
            while (@index < @str.length)
                ch = _GetNextCharToken()
                if ch == '"'
                    break;
                end;
                if ch == "\\";
                    ch2 = _GetNextCharToken();
                    if ch2 == '"'
                        retval += ch2
                        next;
                    elsif ch2 == 'u'
                        code = '    ';
                        code[0] = _GetNextCharToken()[0];
                        code[1] = _GetNextCharToken()[0];
                        code[2] = _GetNextCharToken()[0];
                        code[3] = _GetNextCharToken()[0];
                        
                        a = code.hex
                        if (a <= 0x7ff)
                            s = '--';
                            s[0]=192 + a / 64
                            s[1]=128 + a % 64
                            retval += s;
                        else
                            retval += '?';
                        end
                        next
                    else
                        #retval += ch
                        retval += ch2
                        next;
                    end
                end
                # TODO
                retval += ch
            end
            return retval
        end
        
        def _DecodeNumber()
            retval = "";
            while (true)
                if _IsTokens('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '-', '+')
                    t = _GetTokens('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '-', '+')
                    retval += t
                else
                    break
                end
            end
            
            return retval.to_f
        end
        
        def _DecodeArray()
            _GetToken('[');
            retval = [];
            complete = false;
            while (true)
                break if _IsToken(']')
                retval << _DecodeValue();
                _SkipToken(',')
            end;
            _GetToken(']');
            return retval;
        end
        
        def _DecodeObject()
            _GetToken('{');
            retval = {};
            complete = false;
            
            while (true)
                break if _IsToken('}');
                key = _DecodeString();
                _SkipWhiteSpace()
                _GetToken(':');
                retval[key] = _DecodeValue();
                _SkipToken(',')
            end;
            _GetToken('}');
            return retval;
        end

        def _DecodeValue()
            _SkipWhiteSpace();
            if _IsToken('"')
                return _DecodeString();
            end
            if _IsToken('[')
                return _DecodeArray();
            end
            if _IsToken('{')
                return _DecodeObject();
            end
            if _IsToken('null')
                _GetToken('null')
                return nil;
            end
            if _IsToken('true')
                _GetToken('true')
                return true;
            end
            if _IsToken('false')
                _GetToken('false')
                return false;
            end
            if _IsTokens('-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
                return _DecodeNumber()
            end
            
            raise("Json::_DecodeValue() : %s" % @str[@index..@index + 10]);
        end
        
        #
        # Decodes the given json string to an object. 
        #
        def Decode(str)
            @str = str;
            @index = 0;
            return _DecodeValue();
        end
        
    end
    
end


